/**
 * @FileName: CcbService.java
 * @Author
 * @Description:
 * @Date 2016年12月1日 上午10:50:28
 * @CopyRight ZTE Corporation
 */
package cn.hpclub.server.service;

import java.io.IOException;
import java.util.Calendar;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.net.MediaType;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Record;

import cn.hpclub.server.ccbpay.ServiceType;
import cn.hpclub.server.constant.ClientApiConstant;
import cn.hpclub.server.constant.Constant;
import cn.hpclub.server.constant.H5ApiConstant;
import cn.hpclub.server.exception.CheckOrderException;
import cn.hpclub.server.model.CmccOrder;
import cn.hpclub.server.model.RechargeOrder;
import cn.hpclub.server.model.SysCodeType;
import cn.hpclub.server.model.SysCodeValue;
import cn.hpclub.server.util.HttpUtil;
import cn.hpclub.server.util.SystemConfigUtil;
import cn.hpclub.server.util.Utils;
import cn.hpclub.server.util.wxpay.WxpayConfig;

public class CcbService{
    private static final Logger logger                  = LoggerFactory.getLogger(CcbService.class);
    public static CcbService    service                 = new CcbService();
    private static final String RETURN_CODE             = "RETURN_CODE";
    private static final String RETURN_MSG              = "RETURN_MSG";
    private static final String SUC_CODE                = "000000";

    public static final String  XHRW_SUFFIX_K           = "K";
    public static final String  CZJF_SUFFIX_C           = "C";

    private static final String ORDER_NOT_EXIST         = "订单不存在";
    private static final String PAY_STATUS_ERROR        = "该订单支付状态不能退款：";
    private static final String PAY_STATUS_ERROR_SEARCH = "该订单支付状态不能查询：";
    private static final String AMOUNT_ERROR            = "退款金额有误";
    public static final String  ILLEGAL_ORDER           = "非法订单，暂只支持选号入网和充值缴费订单";
    public static final String  ILLEGAL_ORDER_SARCH     = "非法订单，暂只支持充值缴费订单";

    /**
     * @Title: webRefund
     * @Description:后台退费接口，能看到菜单的都可以退费，其退费开关 由权限来控制
     * @param orderId
     * @param amount
     * @return JSONObject
     */
    public JSONObject webRefund(String orderId, String amount){
        return refund(orderId, amount);
    }

    /**
     * @Title: appRefund
     * @Description:APP自动退费接口，其开关由后台配置控制。选号入网和充值缴费的建行退费统一进行控制。 refund捕获了异常，
     *                                                         理论上改方法不会抛出异常
     * @param orderId
     * @param amount
     * @return JSONObject
     */
    public JSONObject appRefund(String orderId, String amount){
        JSONObject jo = null;
        try{
            if(isAppRefundOpen()){
                return refund(orderId, amount);
            }
            // APP退费功能关闭的情况下
            jo = setResponseObj(ClientApiConstant.ResultCode.FAILED, "APP退费功能关闭", ClientApiConstant.Msg.EMPTY);
            updateOrder(orderId, false, "APP退费功能关闭");
        }
        catch(Exception e){
            e.printStackTrace();
            jo = setResponseObj(ClientApiConstant.ResultCode.FAILED, "APP退费异常" + Utils.getShortMsg(e.getMessage()),
                                ClientApiConstant.Msg.EMPTY);
        }
        return jo;

    }

    private boolean isAppRefundOpen(){
        String value = "";
        List<Record> list = SysCodeType.dao.getSysCodeTypeByCode("CCB_REFUND_APP_SWITCH");
        if(list.size() > 0){
            value = list.get(0).getStr(SysCodeValue.VALUE);
            logger.info("ccb app refund switch:" + value);
            if("1".equals(value)){
                return true;
            }
        }

        return false;

    }

    // boolean无法将错误原因msg带出，暂直接返回JSONObject
    private JSONObject refund(String orderId, String amount){
        logger.info("to refund");

        JSONObject jo = setResponseObj(ClientApiConstant.ResultCode.FAILED, ClientApiConstant.Msg.FAIL,
                                       ClientApiConstant.Msg.EMPTY);
        try{
            logger.info(orderId + ":" + amount);

            // 核对订单数据,不正确会抛异常，都正确才能往下走
            checkOrder(orderId, amount);

            String r = HttpUtil.getHttpByPostBody(getRefundUrl(), "orderId=" + orderId + "&amount=" + amount);
            logger.debug("resultJsonString:" + r);
            if(r.contains("exception")){
                throw new Exception("连接退款服务器网络错误");
            }

            JSONObject result = new JSONObject(r);
            // JSONObject result = new JSONObject();
            // result.put(RETURN_CODE, SUC_CODE + "");
            // result.put(RETURN_MSG, "msg");

            if(result.getString(RETURN_CODE).equals(SUC_CODE)){
                // 成功则修改订单状态。
                updateOrder(orderId, true, "退费成功:" + r);
                jo = setResponseObj(ClientApiConstant.ResultCode.SUCCESS_CODE, "退款成功", ClientApiConstant.Msg.EMPTY);
            } else{
                // 失败则提示原因
                String msg = result.getString(RETURN_CODE) + ":" + result.getString(RETURN_MSG);
                updateOrder(orderId, false, msg);
                jo = setResponseObj(ClientApiConstant.ResultCode.FAILED, msg, ClientApiConstant.Msg.EMPTY);
            }

        }
        catch(CheckOrderException e){
            // 订单不能发起退费的情况，不能修改订单支付状态为退费失败，否则再下次就可以退费了。
            jo = setResponseObj(ClientApiConstant.ResultCode.SERVER_EXCEPTION, e.getMessage(),
                                ClientApiConstant.Msg.EMPTY);
            e.printStackTrace();
        }
        catch(Exception e){
            // 请求没有响应或者出错，提示原因
            jo = setResponseObj(ClientApiConstant.ResultCode.SERVER_EXCEPTION, e.getMessage(),
                                ClientApiConstant.Msg.EMPTY);
            String errMsg = "" + e.getMessage();
            updateOrder(orderId, false, (errMsg.length() > 50 ? errMsg.substring(0, 50) : errMsg));
            e.printStackTrace();
        }
        return jo;
    }

    // public JSONObject refund(String orderId, String amount){
    // logger.debug("to refund");
    // JSONObject jo = setResponseObj(ClientApiConstant.ResultCode.FAILED,
    // "暂不支持退款", ClientApiConstant.Msg.EMPTY);
    // return jo;
    // }

    private boolean checkOrder(String orderId, String amount) throws Exception{
        if(orderId.startsWith(XHRW_SUFFIX_K)){
            return checkCmccOrder(orderId, amount);
        } else if(orderId.startsWith(CZJF_SUFFIX_C)){
            return checkRechargeOrder(orderId, amount);
        }
        throw new Exception(ILLEGAL_ORDER + ":" + orderId);

    }

    private boolean checkCmccOrder(String orderId, String amount) throws Exception{
        CmccOrder record = CmccOrder.dao.getOrderInfo(orderId);
        if(record == null){
            throw new CheckOrderException(ORDER_NOT_EXIST);
        }
        int payStatus = record.getInt(CmccOrder.PAY_STATUS);
        // 已支付或者退款失败的，才能执行退款。其他（未支付已退款等）均不能再退款。
        if((payStatus != CmccOrder.PAID) && (payStatus != CmccOrder.REFUND_FAILED)){
            throw new CheckOrderException(PAY_STATUS_ERROR + payStatus);
        }
        Double payAmount = record.getDouble(CmccOrder.PAY_AMOUNT);
        if(payAmount.compareTo(Double.valueOf(amount)) < 0){
            throw new CheckOrderException(AMOUNT_ERROR + ":payAmount=" + payAmount + ":amount=" + amount);
        }
        return true;
    }

    private boolean checkRechargeOrder(String orderId, String amount) throws Exception{
        RechargeOrder record = RechargeOrder.dao.getRechargeOrderByOrderId(orderId);
        if(record == null){
            throw new CheckOrderException(ORDER_NOT_EXIST);
        }
        int payStatus = record.getInt(RechargeOrder.PAY_STATUS);
        // 已支付或者退款失败的，才能执行退款。其他（未支付已退款等）均不能再退款。
        if((payStatus != RechargeOrder.PAID) && (payStatus != RechargeOrder.REFUND_FAILED)){
            throw new CheckOrderException(PAY_STATUS_ERROR + payStatus);
        }
        Double payAmount = record.getDouble(RechargeOrder.PAY_AMOUNT);
        if(payAmount.compareTo(Double.valueOf(amount)) < 0){
            throw new CheckOrderException(AMOUNT_ERROR + ":payAmount=" + payAmount + ":amount=" + amount);
        }
        return true;
    }

    /**
     * @Title: updateOrder
     * @Description:TODO
     * @param orderId
     *            void
     */

    private void updateOrder(String orderId, boolean result, String msg){
        if(orderId.startsWith(XHRW_SUFFIX_K)){
            // CmccOrder.dao.updateRefund(orderId, result ? CmccOrder.REFUNDED :
            // CmccOrder.REFUND_FAILED, msg);
            CmccOrder.dao.updateRefund(orderId, result ? CmccOrder.REFUNDED : CmccOrder.REFUND_FAILED, msg, result);
        } else if(orderId.startsWith(CZJF_SUFFIX_C)){
            // RechargeOrder.dao.updateRefund(orderId, result ?
            // RechargeOrder.REFUNDED : RechargeOrder.REFUND_FAILED, msg);
            RechargeOrder.dao.updateRefund(orderId, result ? RechargeOrder.REFUNDED : RechargeOrder.REFUND_FAILED, msg,
                                           result);
        }
    }

    private String getRefundUrl(){
        String url = "";
        List<Record> list = SysCodeType.dao.getSysCodeTypeByCode("CCB_REFUND_URL");
        if(list.size() > 0){
            url = list.get(0).getStr(SysCodeValue.VALUE);
            logger.info("ccb refund url:" + url);
            return url;
        }

        return url;

    }

    private org.json.JSONObject setResponseObj(int code, String msg, Object data){
        org.json.JSONObject rjson = new org.json.JSONObject();

        rjson.put(H5ApiConstant.Key.RESULT_CODE, code);
        rjson.put(H5ApiConstant.Key.MESSAGE, msg);

        if(data != null){
            rjson.put(H5ApiConstant.Key.DATA, data);
        }

        return rjson;
    }

    /**
     * @Title: timeLimite
     * @Description:每天0点前后30分钟内不能办理业务。原因：建行对账
     * @return boolean 采用配置需要配置多个数据+时分时间变动不频繁，写死在代码中。
     */
    public boolean timeLimit(){
        Calendar calendar = Calendar.getInstance();
        System.out.println("hour in day:" + calendar.get(Calendar.HOUR_OF_DAY)); // 24小时制

        int hour = calendar.get(Calendar.HOUR_OF_DAY);// 获取到当前的小时,24小时制

        // 0点过30分之前，禁止
        if(hour == 0){
            int min = calendar.get(Calendar.MINUTE);
            System.out.println("min:" + min);
            if(min <= 30){
                return true;
            }
            return false;

        } else if(hour == 23){
            int min = calendar.get(Calendar.MINUTE);
            System.out.println("min:" + min);
            // 0点差30分之后，禁止
            if(min >= 30){
                return true;
            }
            return false;
        } else{
            return false;
        }

    }

    /**
     * @Title: ccbSearch
     * @Description:调建行查询接口进行查询
     * @param orderId
     * @return JSONObject
     * @throws Exception
     */
    public JSONObject ccbSearch(String orderId) throws Exception{
        logger.info("----ccbSearch----");
        JSONObject result = null;
        String posId = "";
        try{
            logger.info(orderId);

            // posId = getPosIdByOrderId(orderId);// 根据orderId获取柜台号

            String data = "";
            if(StrKit.notBlank(orderId)){
                data = ("orderId=" + orderId);
                if(StrKit.notBlank(posId)){
                    data += ("&pos=" + posId);
                }
            } else if(StrKit.notBlank(posId)){
                data = ("pos=" + posId);
            }
            logger.debug("request data = " + data);
            // logger.info("request data = " + data);
            String r = HttpUtil.getHttpByPostBody(getCcbSearchUrl(), data);

            logger.debug("resultJsonString:" + r);
            // logger.info("ccb search result: " + r);
            if(r.contains("exception")){
                throw new Exception("连接查询服务器网络错误");
            }

            result = parseResult(r);
        }
        catch(JSONException e){
            e.printStackTrace();
            logger.error("JSONException: " + e.getMessage() + orderId + ":" + posId);
            throw new Exception(e.getMessage());
        }
        catch(CheckOrderException e){
            e.printStackTrace();
            logger.error("CheckOrderException: " + e.getMessage() + orderId + ":" + posId);
            throw new Exception(e.getMessage());
        }
        catch(CcbSearchOrderException e){
            e.printStackTrace();
            logger.error("CcbSearchOrderException: " + e.getMessage() + orderId + ":" + posId);
            throw new Exception(e.getMessage());
        }
        catch(CheckCcbOrderStatusException e){// 该异常不是真正的异常，单独抛出
            e.printStackTrace();
            logger.error("CheckCcbOrderStatusException: " + e.getMessage() + orderId + ":" + posId);
            throw new CheckCcbOrderStatusException(e.getMessage());
        }
        catch(Exception e){
            e.printStackTrace();
            logger.error("Exception：" + e.getMessage() + "：" + orderId + "：" + posId);
            throw new Exception(e.getMessage());
        }

        if(result == null){
            // 调用查询接口错误，或者没有查询结果
            logger.error("ccb search order error: " + orderId);
            logger.error("result is null：" + orderId + "：" + posId);
            throw new Exception("调用查询接口错误，或者没有查询结果");
        }

        return result;
    }

    /**
     * @Title: getPosIdByOrderId
     * @Description:TODO
     * @param orderId
     * @return String
     * @throws Exception
     */
    private String getPosIdByOrderId(String orderId) throws Exception{
        if(getOrderType(orderId) == ServiceType.RECHARGE.getValue()){
            Record orderRecord = RechargeOrderService.service.getRechargeOrderByOrderId(orderId);

            // "30819c300d06092a864886f70d010101050003818a003081860281806c5ce0ed0655b741d546503f041f1c919bb3bbbf88d7d1549258208619d96df352cd385fb7699a13c4d95c020ab55e5723e64d783180974d6605a6d7db3579b4c8d0751d9e917631e45844a5fd1bf3dba2aa7958b4f823401b46db3274173063b886b1dfd9056235bef55d56811f593e787d8a26103eeaeb3af4ce00a627bc63020111";
            return orderRecord.getStr("pos_id");
        } else{
            Record order = CmccOrder.dao.getOrder(orderId);
            return order.getStr("pos_id");
        }
    }

    /**
     * @Title: parseResult
     * @Description:TODO
     * @param r
     * @return JSONObject
     * @throws CcbSearchOrderException：查询服务器返回明确错误码时抛出
     * @throws JSONException：json解析异常时抛出
     * @throws CheckCcbOrderStatusException：查询返回正确但状为非成功状态时抛出
     */
    private JSONObject parseResult(String r)
            throws JSONException, CcbSearchOrderException, CheckCcbOrderStatusException{
        JSONObject result = new JSONObject(r);
        JSONObject rs = null;

        if(SUC_CODE.equals(result.getString(RETURN_CODE))){
            String orderStatus = result.getJSONObject("TX_INFO").getJSONObject("LIST").getString("ORDER_STATUS");
            logger.info("ccb orderStatus: " + orderStatus);
            switch(orderStatus){
                case "1":// 1:成功
                    rs = result;
                    break;
                case "0":// 0:失败
                    throw new CheckCcbOrderStatusException("支付失败或者取消支付");
                case "2":// 2:待银行确认
                    throw new CheckCcbOrderStatusException("待银行确认");
                case "3":// 3:已部分退款
                    throw new CheckCcbOrderStatusException("已部分退款");
                case "4":// 4:已全额退款
                    throw new CheckCcbOrderStatusException("已全额退款");
                case "5":// 5:待银行确认
                    throw new CheckCcbOrderStatusException("待银行确认");
                default:
                    break;
            }

        } else{// 不是明确成功都抛出异常，包括建行异常和dzg建行服务器异常
            logger.error("CcbSearchOrderException: " + r);
            throw new CcbSearchOrderException(result.getString(RETURN_MSG));
        }
        return rs;
    }

    /**
     * @Title: checkSearchOrder
     * @Description:TODO
     * @param orderId
     *            boolean
     * @throws Exception
     */
    public boolean checkSearchOrder(String orderId) throws CheckOrderException, Exception{
        if(orderId.startsWith(XHRW_SUFFIX_K)){
            return checkCmccOrder(orderId);
        } else if(orderId.startsWith(CZJF_SUFFIX_C)){
            return checkRechargeOrder(orderId);
        }
        throw new Exception(ILLEGAL_ORDER + ":" + orderId);
    }

    /**
     * @Title: checkRechargeOrder
     * @Description:TODO
     * @param orderId
     * @return boolean
     * @throws CheckOrderException
     */
    private boolean checkRechargeOrder(String orderId) throws CheckOrderException{
        RechargeOrder record = RechargeOrder.dao.getRechargeOrderByOrderId(orderId);
        if(record == null){
            throw new CheckOrderException(ORDER_NOT_EXIST);
        }
        int payStatus = record.getInt(RechargeOrder.PAY_STATUS);
        // 未支付才去查询，其他状态都不再查询
        if(payStatus == RechargeOrder.PAID){// 已支付返回
            return false;
        } else if(payStatus != RechargeOrder.NOT_PAID){
            throw new CheckOrderException(PAY_STATUS_ERROR_SEARCH + payStatus);
        }

        return true;
    }

    /**
     * @Title: checkCmccOrder
     * @Description:TODO
     * @param orderId
     * @return boolean
     * @throws CheckOrderException
     */
    private boolean checkCmccOrder(String orderId) throws CheckOrderException{
        CmccOrder record = CmccOrder.dao.getOrderInfo(orderId);
        if(record == null){
            throw new CheckOrderException(ORDER_NOT_EXIST);
        }
        int payStatus = record.getInt(CmccOrder.PAY_STATUS);
        // 未支付才去查询，其他状态都不再查询
        if(payStatus == CmccOrder.PAID){// 已支付返回
            return false;
        } else if(payStatus != CmccOrder.NOT_PAID){
            throw new CheckOrderException(PAY_STATUS_ERROR_SEARCH + payStatus);
        }
        return true;
    }

    /**
     * @Title: getCcbSearchUrl
     * @Description:TODO
     * @return String
     */
    private String getCcbSearchUrl(){
        String url = "";
        List<Record> list = SysCodeType.dao.getSysCodeTypeByCode("CCB_HOST");
        if(list.size() > 0){
            url = list.get(0).getStr(SysCodeValue.VALUE);
            logger.info("ccb search url:" + url + "/ccb/query");
            return url + "/ccb/query";
        }

        return url;
    }

    /**
     * 查询接口返回明确错误码时抛的异常
     */
    public static class CcbSearchOrderException extends Exception{

        private static final long serialVersionUID = 5499004252047126491L;

        public CcbSearchOrderException(String message){
            super(message);
        }
    }

    /**
     * 查询接口返回成功，但状态不是支付成功时抛出异常
     */
    public static class CheckCcbOrderStatusException extends Exception{

        private static final long serialVersionUID = -8201957355421200878L;

        public CheckCcbOrderStatusException(String message){
            super(message);
        }
    }

    /**
     * @Title: getOrderType
     * @Description:TODO
     * @param orderId
     * @return String
     * @throws Exception
     */
    public String getOrderType(String orderId) throws Exception{
        if(orderId.startsWith(CcbService.CZJF_SUFFIX_C)){
            return "1";
        }
        // else if(orderId.startsWith(CcbService.XHRW_SUFFIX_K)){
        // return "2";
        // }
        throw new Exception(CcbService.ILLEGAL_ORDER_SARCH + ":" + orderId);
    }

    /**
     * @Title: searchOrder
     * @Description:TODO
     * @param orderId
     *            void
     * @param rechargeType
     * @return
     * @throws Exception
     */
    public JSONObject searchOrder(String orderId, int rechargeType) throws Exception{
        JSONObject jo = null;
        if(StrKit.isBlank(orderId)){// 订单号为空，返回失败
            jo = setResponseObj(H5ApiConstant.ResultCode.FAIL_CODE, H5ApiConstant.Msg.ORDERID_IS_BLANK,
                                H5ApiConstant.Msg.EMPTY);

            return jo;
        }

        String orderIdType = getOrderType(orderId);// 不是充值缴费订单号，抛异常并返回。根据订单号区分是充值缴费还是选号入网业务，目前该功能暂时只针对充值缴费业务

        if(!CcbService.service.checkSearchOrder(orderId)){// 表示订单已支付，返回成功，空对象。订单号为空、不是未支付状态抛异常并返回
            jo = setResponseObj(H5ApiConstant.ResultCode.SUCCESS_CODE, H5ApiConstant.Msg.ORDER_IS_PAIED,
                                new JSONObject());// 封装成成功数据返回

            return jo;
        }

        // 调建行查询接口进行查询，只有支付成功才会返回值，其他情况均抛出异常
        JSONObject ccbResult = ccbSearch(orderId);

        String result = ccbResult.getJSONObject("TX_INFO").getJSONObject("LIST").getString("ORDER_STATUS");
        String amount = ccbResult.getJSONObject("TX_INFO").getJSONObject("LIST").getString("PAYMENT_MONEY");

        ServiceType type = getServiceType(orderIdType);

        if(updateOrder(type, orderId, amount, ("1".equals(result) ? true : false))){
            // 处理业务
            if(type == ServiceType.RECHARGE){
                logger.info("recharge in");
                // 调中移充值接口
                cmccRecharge(orderId, rechargeType);// 1-首次充值、2-二次充值、3-查询更新、4-手动查询充值、5-自动查询充值

                // for test
                // jo = setResponseObj(H5ApiConstant.ResultCode.FAIL_CODE, "do",
                // H5ApiConstant.Msg.EMPTY);
                // return jo;
            } else if(type == ServiceType.PACKAGE){
                // TODO 暂时只对充值缴费业务进行查询和补办
                // 调中移选号入网接口
                // logger.info("openAccount in");
                // openAccount(orderId);

                // for test
                // jo = setResponseObj(H5ApiConstant.ResultCode.FAIL_CODE, "do",
                // H5ApiConstant.Msg.EMPTY);
                // return jo;
            }

        } else{
            // 已经办理过了、或者支付状态不对、或者可能是建行支付回调或者其他业务正在更新该订单，不进行办理业务。
            jo = setResponseObj(H5ApiConstant.ResultCode.FAIL_CODE, H5ApiConstant.Msg.ORDERID_PAYSTATUS_UPDATE_FAILED,
                                H5ApiConstant.Msg.EMPTY);

            // for test
            // jo = setResponseObj(H5ApiConstant.ResultCode.FAIL_CODE, "donot
            // do", H5ApiConstant.Msg.EMPTY);

            return jo;
        }

        jo = setResponseObj(H5ApiConstant.ResultCode.SUCCESS_CODE, H5ApiConstant.Msg.SUCCESS, ccbResult);// 建行查询结果返回给h5前端

        return jo;
    }

    /**
     * @Title: cmccRecharge
     * @Description:充值缴费
     * @param orderId：订单号
     * @param rechargeType：充值类型：
     *            1-首次充值、2-二次充值、3-查询更新、4-手动查询充值、5-自动查询充值 void
     */
    private void cmccRecharge(String orderId, int rechargeType){
        new RechargeOrderService().cmccRechargeAndRefund(orderId, rechargeType);
    }

    /**
     * @Title: updateOrder
     * @Description:TODO
     * @param type
     * @param orderId
     * @param amount
     * @param b
     * @return boolean
     */
    private boolean updateOrder(ServiceType type, String orderId, String amount, boolean result){
        if(type == ServiceType.RECHARGE){
            // return CmccRechargeService.service.updateRechargeOrder(orderId,
            // amount, result);
            return CmccRechargeService.service.updateRechargeOrderNew(orderId, amount, result);
        } else if(type == ServiceType.PACKAGE){
            return CmccPackageService.service.updatePackageOrder(orderId, amount, result);
        }
        // 其他的类型抛弃
        return false;
    }

    /**
     * @Title: getServiceType
     * @Description:TODO
     * @param orderIdType
     * @return ServiceType
     */
    private ServiceType getServiceType(String value){
        if(value.equals(ServiceType.RECHARGE.getValue())){
            return ServiceType.RECHARGE;
        } else if(value.equals(ServiceType.PACKAGE.getValue())){
            return ServiceType.PACKAGE;
        } else{
            return ServiceType.UNKOWN;
        }
    }
}
